import qs from 'qs';
import EventEmitter from 'eventemitter3';
import { wsql, pingql } from './ql';
import { parseJson } from './helper';
import type { SocketOptions, EventType, EventHandler } from './types';

export class SocketPro {
  private url: string;
  private opts: SocketOptions;
  private ee = new EventEmitter();
  private heartbeatTimoutCount = 0;
  private ws: WebSocket | undefined;
  private threadId: string | undefined;
  private heartbeatTimer: NodeJS.Timeout | undefined;
  private heartbeatTimer1: NodeJS.Timeout | undefined;
  private heartbeatTimer2: NodeJS.Timeout | undefined;
  private sMsg: string | undefined;

  constructor(url: string, opts: SocketOptions) {
    this.url = url;
    this.opts = opts;
    this.createSocket();
  }

  private get wsUrl() {
    const { apiKey, ...rest } = this.opts;
    const queryStr = qs.stringify({ ...rest, ['x-api-key']: apiKey });
    return [this.url, queryStr].join('?');
  }

  // 创建socket实列
  private createSocket = (autoSend = false) => {
    this.ws = new WebSocket(this.wsUrl);
    this.ws.onmessage = this.onMessage;
    this.ws.onopen = () => this.wsOpen(autoSend);
  }

  // ws打开
  private wsOpen = (autoSend: boolean) => {
    // 20 秒后开始心跳
    this.runHeartbeat(20);
    this.ee.emit('connect', this.ws);
    // 重新连接以后，如果存在消息，直接发送
    if (autoSend && this.sMsg) {
      this.ws?.send(this.sMsg);
      this.sMsg = undefined;
    }
  }

  // 清除时间
  private clearTimer = () => {
    clearTimeout(this.heartbeatTimer);
    clearTimeout(this.heartbeatTimer1);
    clearTimeout(this.heartbeatTimer2);
  }

  // 收到消息
  private onMessage = (evt: MessageEvent) => {
    // 后端报错
    const data = evt.data as string;
    const error = 'Internal Server Error';

    if (data.includes(error)) {
      this.closeConnect();
      console.error(error);
      return;
    }

    this.sMsg = undefined;
    const result = parseJson(data);

    if (!result.data) {
      this.ee.emit('message', result);
      return;
    }

    const ping = result.data.ping;
    const askOpenAi = result.data.askOpenAi;
    if (ping) {
      this.ee.emit('ping');
    }
    if (askOpenAi?.threadId) {
      this.threadId = askOpenAi.threadId;
    }
  }

  private runHeartbeat = (ms: number = 0) => {
    this.heartbeatTimer2 = setTimeout(
      this.startHeartbeat, ms * 1000
    );
  }

  // 开始心跳
  private startHeartbeat = () => {
    this.send(pingql);
    // 超时时间为30s
    this.heartbeatTimer = setTimeout(
      this.heartbeatTimeout, 30 * 1000
    );
    this.ee.once('ping', this.receivedPing);
    console.log('心跳检测: ping');
  }

  private closeConnect = () => {
    this.heartbeatTimoutCount = 0;
    this.clearTimer();
    this.ws?.close();
  }

  // 连接超时
  private heartbeatTimeout = () => {
    this.heartbeatTimoutCount++;
    this.ee.off('ping', this.receivedPing);
    // 大于3次，重新连接
    if (this.heartbeatTimoutCount >= 3) {
      this.closeConnect();
      this.createSocket(true);
      console.log('连接失败，正在重连...');
    } else {
      this.runHeartbeat(5);
      const count = this.heartbeatTimoutCount;
      console.log(`心跳检测:超时(${count})`);
    }
  }

  // 接收到ping
  private receivedPing = () => {
    this.clearTimer();
    this.runHeartbeat(30);
    console.log('心跳检测: pong');
  }

  public on = (type: EventType, handler: EventHandler) => {
    this.ee.on(type, handler);
    return this;
  }

  public off = (eventType: EventType, handler: EventHandler) => {
    this.ee.off(eventType, handler);
    return this;
  }

  private send = (ql: string, options = {}, variables = {}) => {
    const payload = JSON.stringify({
      query: ql,
      variables
    });
    const message = JSON.stringify({
      ...options,
      payload,
      action: 'stream',
      endpointId: 'openai',
      funct: 'openai_assistant_graphql'
    });
    this.ws?.send(message);
    return message;
  }

  // 发送消息给后端
  public sendMessage = (input: string) => {
    const options = {
      region: 'US',
      company_id: 'XXXXXXXX',
      question_groups: '12345678',
      place_uuid: '2182613588100714991'
    };
    const variables = {
      userQuery: input,
      updatedBy: "Use XYZ",
      threadId: this.threadId,
      assistantType: "conversation",
      assistantId: "asst_Xrt7Ls4Arhj4QV71mtxJcYqm",
    };
    this.sMsg = this.send(wsql, options, variables);
  }

  public closeAllMessage = () => {
    this.ee.off('message');
  }

  public destroy = () => {
    this.ws!.close();
    this.clearTimer();
    this.ee.removeAllListeners();
  }
}